#include "GameWorld.h"
#include "BaseEntity.h"
#include "Utils.h"

/* GameWorld():
 *		Constructor.
 */
GameWorld::GameWorld( sf::RenderWindow* win )
{
	Utils::debugAssert( win != nullptr, "Attempting to create a GameWorld without a valid RenderWindow." );
	this->win = win;

	// set the world height/width as the same
	// as the window height/width by default
	setWorldSize( win->getSize().x, win->getSize().y );
	setNumberViews( 1 );

	splitHorizontal = false;
	currentView = VIEW_ONE;

	for( int i = VIEW_ONE; i < MAX_VIEWS; ++i )
	{
		followEntities[i] = false;
		centralEntities[i] = nullptr;
	}
}

/* update():
 *		The update function for the game world.
 */
void GameWorld::update(float dt)
{
	for( currentView = VIEW_ONE; currentView < activeViews; ++currentView )
	{
		win->setView( views[currentView] );

		if( followEntities[currentView] && centralEntities[currentView] != nullptr )
		{
			setViewPosition( centralEntities[currentView]->getPosition(), currentView );
		}
	}

	for( auto it = entities.begin(); it != entities.end(); ++it)
	{
		(*it)->update(dt);
	}
}

/* draw():
 * Draws a given Entity in the world.
 */
void GameWorld::draw( BaseEntity *entity )
{
	for( currentView = VIEW_ONE; currentView < activeViews; ++currentView )
	{
		win->setView( views[currentView] );
		entity->draw();
	}
}

/* draw():
 * Draws a given Drawable in the world.
 */
void GameWorld::draw( const sf::Drawable &drawable )
{
	for( currentView = VIEW_ONE; currentView < activeViews; ++currentView )
	{
		win->setView( views[currentView] );
		win->draw( drawable );
	}
}

/* addEntity():
 *		Adds an Entity to the world.
 */
void GameWorld::addEntity( BaseEntity *entity )
{
	Utils::debugAssert( entity != nullptr, "Attempted to add a nullptr entity to a GameWorld object." );

	entities.push_back( entity );
}

/* removeEntity():
 *		Removes an Entity from the world.
 */
void GameWorld::removeEntity( BaseEntity *entity )
{
	for( vector<BaseEntity*>::iterator i = entities.begin(); i != entities.end(); )
	{
		if( (*i)->ID() == entity->ID() )
		{
			entities.erase( i );
			return;
		}

		++i;
	}

	return;
}

/* splitHorizontally():
 *		Switches from vertical split screen to horizontal
 *		(flag == true) or back again (flag == false).
 *		will only affect two-view split screen.
 */
void GameWorld::splitHorizontally( bool flag )
{
	splitHorizontal = flag;
	setNumberViews( activeViews );

	// loop through the views and reset their positions,
	// just in case the views have moved somewhere dodgy
	// during setNumberViews(). (can happen when changing
	// a two-screen split).
	for( int i = VIEW_ONE; i < activeViews; ++i )
	{
		setViewPosition( viewPositions[i], i );
	}
}

/* setWorldSize():
 *		Sets the size of the world.
 */
void GameWorld::setWorldSize( int width, int height )
{
	worldWidth = width;
	worldHeight = height;
}

void GameWorld::setViewPosition( Vector2D position, int view )
{
	viewPositions[view].x = CAP( position.x, worldWidth - views[view].getSize().x / 2, views[view].getSize().x / 2 );
	viewPositions[view].y = CAP( position.y, worldHeight - views[view].getSize().y / 2, views[view].getSize().y / 2 );
	views[view].setCenter( viewPositions[view].sfVector() );
}

/* moveViewUp():
 *		Moves the view up by a given amount.
 */
void GameWorld::moveViewUp( unsigned int amount, int view )
{
	setViewPosition( V2D( viewPositions[view].x, viewPositions[view].y - amount ), view );
}

/* moveViewDown():
 *		Moves the view down by a given amount.
 */
void GameWorld::moveViewDown( unsigned int amount, int view )
{
	setViewPosition( V2D( viewPositions[view].x, viewPositions[view].y + amount ), view );
}

/* moveViewLeft():
 *		Moves the view left by a given amount.
 */
void GameWorld::moveViewLeft( unsigned int amount, int view )
{
	setViewPosition( V2D( viewPositions[view].x - amount, viewPositions[view].y ), view );
}

/* moveViewRight():
 *		Moves the view right by a given amount.
 */
void GameWorld::moveViewRight( unsigned int amount, int view )
{
	setViewPosition( V2D( viewPositions[view].x + amount, viewPositions[view].y ), view );
}

void GameWorld::setNumberViews( int number )
{
	activeViews = CAP( number, 4, 1 );
	float width, height;

	switch( number )
	{
	case 1:
		views[VIEW_ONE].setSize( (float)win->getSize().x, (float)win->getSize().y );
		views[VIEW_ONE].setViewport( sf::FloatRect( 0, 0, 1, 1 ) );
		break;
	case 2:
		width = splitHorizontal ? (float)win->getSize().x : (float)win->getSize().x / 2;
		height = splitHorizontal ? (float)win->getSize().y / 2 : (float)win->getSize().y;
		
		views[VIEW_ONE].setSize( width, height );
		views[VIEW_TWO].setSize( width, height );

		if( splitHorizontal )
		{
			views[VIEW_ONE].setViewport( sf::FloatRect( 0, 0, 1.0f, 0.5f ) );
			views[VIEW_TWO].setViewport( sf::FloatRect( 0, 0.5f, 1.0f, 0.5f ) );
		}
		else
		{
			views[VIEW_ONE].setViewport( sf::FloatRect( 0, 0, 0.5f, 1.0f ) );
			views[VIEW_TWO].setViewport( sf::FloatRect( 0.5f, 0, 0.5f, 1.0f ) );
		}
		break;
	case 3:
		width = (float)win->getSize().x / 2;
		height = (float)win->getSize().y / 2;

		for( int i = VIEW_ONE; i < activeViews; ++i )
		{
			views[i].setSize( width, height );
		}

		views[VIEW_ONE].setViewport( sf::FloatRect(0,0,0.5,0.5) );
		views[VIEW_TWO].setViewport( sf::FloatRect(0.5,0,0.5,0.5) );
		views[VIEW_THREE].setViewport( sf::FloatRect(0,0.5,0.5,0.5) );
		break;
	case 4:
		width = (float)win->getSize().x / 2;
		height = (float)win->getSize().y / 2;

		for( int i = VIEW_ONE; i < activeViews; ++i )
		{
			views[i].setSize( width, height );
		}

		views[VIEW_ONE].setViewport( sf::FloatRect(0,0,0.5,0.5) );
		views[VIEW_TWO].setViewport( sf::FloatRect(0.5,0,0.5,0.5) );
		views[VIEW_THREE].setViewport( sf::FloatRect(0,0.5,0.5,0.5) );
		views[VIEW_FOUR].setViewport( sf::FloatRect(0.5,0.5,0.5,0.5) );
		break;
	default:
		return;
	}
}

/* setCentralEntity():
 *		Sets an entity to keep central to the window.
 */
void GameWorld::setCentralEntity( BaseEntity *entity, int view )
{
	centralEntities[view] = entity;

	if( centralEntities[view] == nullptr )
	{
		followEntities[view] = false;
	}
}

/* enableFollowEntity():
 *		Enables/disables following a central entity (if set).
 */
void GameWorld::enableFollowEntity( bool flag, int view )
{
	if( centralEntities[view] != nullptr )
	{
		followEntities[view] = flag;
		return;
	}

	// no centralEntity set, so set following false.
	followEntities[view] = false;
}

/* getCurrentView():
 *		Returns the current view being drawn.
 */
int GameWorld::getCurrentView() const
{
	return currentView;
}

/* getWorldWidth():
 *		Returns the width of the world.
 */
int GameWorld::getWorldWidth() const
{
	return worldWidth;
}

/* getWorldHeight():
 *		Returns the height of the world.
 */
int GameWorld::getWorldHeight() const
{
	return worldHeight;
}

/* isInView():
 *		Returns true if a given point is within a given view,
 *		or a point within radius around position is in a given view.
 */
bool GameWorld::isInView( Vector2D position, float radius, int view )
{
	int halfWidth = (int)views[view].getSize().x / 2;
	int halfHeight = (int)views[view].getSize().y / 2;

	// check the position is within the constraints of the
	// view width and height.
	bool xmin = (position.x + radius) > (viewPositions[view].x - halfWidth);
	bool xmax = (position.x - radius) < (viewPositions[view].x + halfWidth);
	bool ymin = (position.y + radius) > (viewPositions[view].y - halfHeight);
	bool ymax = (position.y - radius) < (viewPositions[view].y + halfHeight);

	return ( xmin && xmax && ymin && ymax );
}

/* isOnScreen():
 *		Returns true if any point within radius around position is on the screen.
 */
bool GameWorld::isOnScreen( Vector2D position, float radius )
{
	// loop through all of the active views and see if the
	// position is on screen for any of them.
	for( int i = VIEW_ONE; i < activeViews; ++i )
	{
		if( isInView( position, radius, i ) )
		{
			return true;
		}
	}

	// If we got here the position is off 
	// screen on all views, so return false.
	return false;
}

/* getWindow():
 *		Returns a pointer to the window.
 */
sf::RenderWindow* GameWorld::getWindow() const
{
	return win;
}

/* getEntities():
 *		Returns a pointer to the entities vector.
 */
vector<BaseEntity*> GameWorld::getEntities() const
{
	return entities;
}

/* ~GameWorld():
 *		Destructor.
 */
GameWorld::~GameWorld()
{
	entities.clear();
}